#pragma once

#include <d3d11.h>
#include "..\EdgeMath\Vectors.h"
#include "DepthBuffer.h"

#define NUM_SAMPLERS 8


class Shape;
class IDrawable;
class RenderTarget;
class Texture2D;

enum SHADER_INDEX
{
	SHADER_INDEX_VERTEX_SHADER,
	SHADER_INDEX_PIXEL_SHADER,
	SHADER_INDEX_GEOMETRY_SHADER,
};

struct PiplineIndex
{
	SHADER_INDEX shader_index;
	unsigned short slot;
};

class GraphicsManager
{
	friend class ShaderContainer;

	bool began = false;

	struct
	{
		ID3D11Device* device = nullptr;
		ID3D11DeviceContext* device_context = nullptr;
		D3D11_VIEWPORT viewport;
		IDXGISwapChain* swap_chain = nullptr;


		void Release(void)
		{
			device->Release();
			device_context->Release();
			swap_chain->Release();
		}

	} graphics;



	DepthBuffer* depth_buffer = nullptr;

	RenderTarget* render_tgt = nullptr;

	// when set to false, the graphics manager will not bind any shaders
	// - used for drawing deffered lighting, or any effect that involves 
	//   a shader that would not normally be used by the objects in the scene
	bool use_shaders = true;

	static GraphicsManager* instance;

	void SendBufferToDevice(ID3D11Buffer* buffer, PiplineIndex slot);
	GraphicsManager(HWND hwnd, int backBufferWidth, int backBufferHeight);
public:
	~GraphicsManager(void);
	/////////////////////////<Public functions>///////////////////////////////////////////////////
	static void Inilitizie(HWND hwnd, int backBufferWidth, int backBufferHeight);
	static void ShutDown(void);

	void Begin(void);
	void End(void);
	void MakeBuffer(ID3D11Buffer** buffer, D3D11_BUFFER_DESC* buffer_desc, void* subresource_data = nullptr);
	void UpdateBuffer(void* data, PiplineIndex index, unsigned int bufferSize);

	RenderTarget* getBackBuffer(void) const { return render_tgt; }

	ID3D11PixelShader* createPixelShader(void* pData, unsigned int bufferSize);
	ID3D11VertexShader* createVertexShader(void* pData, unsigned int bufferSize);
	ID3D11GeometryShader* createGeometryShader(void* pData, unsigned int bufferSize);

	ID3D11RenderTargetView* createRenderTarget(D3D11_TEXTURE2D_DESC* desc, ID3D11Texture2D** texture_out, D3D11_SUBRESOURCE_DATA* subresource_data = nullptr);
	ID3D11DepthStencilView* createDepthStencilView(D3D11_TEXTURE2D_DESC* desc, ID3D11Texture2D** texture_out, D3D11_SUBRESOURCE_DATA* subresource_data = nullptr);
	ID3D11InputLayout* createInputLayout(D3D11_INPUT_ELEMENT_DESC* layout, const BYTE* shaderCode, unsigned int shaderCodeSize, unsigned int numElements);
	ID3D11ShaderResourceView* createSRV(D3D11_SHADER_RESOURCE_VIEW_DESC* desc, ID3D11Resource* resource);
	ID3D11BlendState* createBlendState(D3D11_BLEND_DESC* desc);
	ID3D11DepthStencilState* createStencilState(D3D11_DEPTH_STENCIL_DESC* desc);
	
	ID3D11SamplerState* createSamplerState(D3D11_SAMPLER_DESC* desc);

	void AllocateContextSlot(unsigned int index);

	void Draw(IDrawable* shape);

	void ClearDepthBuffer(float clearValue);
	void ClearBackBuffer(Vector4& clearColor);

	/////////////////////////<Accessors>///////////////////////////////////////////////////
	static GraphicsManager* GetInstance(void);

	bool GetUseShaders(void) const { return use_shaders; }

	ID3D11Device* getDevice(void) const { return graphics.device; }
	ID3D11DeviceContext* getDeviceContext(void) const { return graphics.device_context; }
	ID3D11SamplerState* getSamplerState(void) const;

	ID3D11PixelShader* getPixelShader(void) const;
	ID3D11VertexShader* getVertexShader(void) const;

	D3D11_VIEWPORT getViewport(void) const { return graphics.viewport; }
		
	DepthBuffer* getDepthBuffer(void) const { return depth_buffer; }

	ID3D11DepthStencilState* getDepthStencilState(void) const;

	ID3D11RasterizerState* getRasterizerState(void) const;

	/////////////////////////<Mutators>///////////////////////////////////////////////////

	void SetUseShaders(bool value) { use_shaders = value; }

	void setSamplerState(ID3D11SamplerState* value);

	void setRenderTarget(ID3D11RenderTargetView* value);

	void setViewPort(D3D11_VIEWPORT& value) { graphics.viewport = value; }

	void setBlendState(ID3D11BlendState* value);

	void setStencilState(ID3D11DepthStencilState* value);

	void setRasterizerState(ID3D11RasterizerState* value);
};